package algo;
import geom.Cercle;
import geom.Point_2D;
import geom.Polygone;
import gui.FenetAffiche;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.Vector;

public class AlgoElastique extends Thread{
	public static int thNumber=0;
	FenetAffiche frame;
	Graphics zoneDessin;
	private Polygone polygonVilles;
	private Point_2D pointsElastique[]; // points de l'elastique
	private Vector<Point_2D> villes; // points correspondants aux villes
	// parametres
	private int R; // rayon du cercle initial
	private int N; // nombre de villes
	private int nbPointsElas; // nombre de points qui composent l'elastique
	private double K; // force de frottement pour rendre le syst�me stable
	private final double alpha; // constante param�tr�e
	private final double beta; // constante param�tr�e
	private final int nbIterations; // nombre d'iterations
	private int ite;
	private double longueurElastique;

	public double getLongueurElastique() {
		return longueurElastique;
	}

	public AlgoElastique(Polygone polyVilles, int r, double m, double k,
			double alpha, double beta, int nbIterations, FenetAffiche frame) {
		this.alpha = alpha;
		this.beta = beta;
		this.K = k;
		this.R = r;
		this.polygonVilles = polyVilles;
		this.villes = polygonVilles.points;
		this.nbIterations = nbIterations;
		this.frame = frame;
		this.zoneDessin = frame.zd.getGraphics();
		this.N = villes.size();
		this.nbPointsElas = (int)(N * m);
	}

	public void letsGo() throws InterruptedException {
		// Initialisation de l'elastique
		init();
		this.dessineToi();
		for (ite = 0; ite < nbIterations; ite++) {
			iterer();
			if(this.isInterrupted()){
				System.out.println("EXITED");
				return;
			}
		}
		longueurElastique=this.calculLongueurElastique();
	}

	// methode qui initialise le cercle de depart
	// et les points de l'elastique
	private void init() throws InterruptedException {
		// on calcul le centre de gravite
		double gX=0;
		double gY=0;
		longueurElastique=0;
		for(int i=0;i<N;i++){
			gX+=villes.get(i).getX();
			gY+=villes.get(i).getY();
		}
		gX/=N;
		gY/=N;
		// on nettoye le canvas
		frame.zd.repaint();
		Thread.sleep(100);
		// on initialise le cercle de depart
		Cercle init = new Cercle("", Color.RED, Color.BLUE,
				(int)gX,(int)gY, R);
		init.dessineToi(zoneDessin);
		// on initialise l'elastique en divisant le cercle en n*m divisions
		double angleDiv = Math.PI * 2. / nbPointsElas;
		//System.out.println("angleDiv:"+angleDiv);
		pointsElastique = new Point_2D[nbPointsElas];
		for (int pt = 0; pt < nbPointsElas; pt++) {
			double currentAngle = angleDiv * pt;
			//System.out.println("currentAngle:"+currentAngle);
			int x = (int) (Math.cos(currentAngle) * R + init.getCentre().getX());
			int y = (int) (Math.sin(currentAngle) * R + init.getCentre().getY());
			//System.out.println("init pt:"+pt+" x:"+x+" y:"+y+"centre:"+init.getCentre());
			Point_2D p = new Point_2D(x, y);
			pointsElastique[pt] = p;
		}
	}

	private void iterer() throws InterruptedException{
		//On applique les forces � tous les points de l'elastique
		for(int j=0;j<nbPointsElas;j++){
			//On recupere le point de l'elastique courant
			Point_2D ptElas=pointsElastique[j];
			//On calcule les forces d'attraction des villes sur ce point
			double forceAttracVillesX=this.forceAttracVillesX(j);
			double forceAttracVillesY=this.forceAttracVillesY(j);
			//On calcule la resistance de l'elastique
			double resistanceElastX=this.resistanceElastiqueX(j);
			double resistanceElastY=this.resistanceElastiqueY(j);
			//On calcule les delta
			double dx=forceAttracVillesX+resistanceElastX;
			double dy=forceAttracVillesY+resistanceElastY;
			//System.out.println("dx:"+dx+" dy:"+dy);
			//On deplace le point
			ptElas.deplace((int)dx, (int)dy);
		}
		//Fin de l'iteration
		K*=0.99; //On diminue de 1% a chaque appel
		this.dessineToi();
		//Temporisation
		//Thread.sleep(100);
	}
	
	//force de resistance de l'elastique au point j avec ses 2 voisins sur X
	private double resistanceElastiqueX(int j){
		double resistanceX;
		int voisin1,voisin2;
		voisin1=(j==0)?nbPointsElas-1:j-1;
		voisin2=(j + 1) % nbPointsElas;
		resistanceX = beta
				* (pointsElastique[voisin1].getX() - pointsElastique[j].getX() + pointsElastique[voisin2].getX() - pointsElastique[j].getX());
		return resistanceX;
	}
	
	//force de resistance de l'elastique au point j avec ses 2 voisins sur Y
	private double resistanceElastiqueY(int j){
		double resistanceY;
		int voisin1,voisin2;
		voisin1=(j==0)?nbPointsElas-1:j-1;
		voisin2=(j + 1) % nbPointsElas;
		resistanceY = beta
				* (pointsElastique[voisin1].getY() - pointsElastique[j].getY() + pointsElastique[voisin2].getY() - pointsElastique[j].getY());
		return resistanceY;
	}

	//force d'attraction des villes sur le point j sur X
	private double forceAttracVillesX(int j) {
		double force = 0.;

		for (int i = 0; i < N; i++) {
			double distance = villes.get(i).getX() - pointsElastique[j].getX();
			force += poids(i, j) * distance;
		}

		return alpha*force;
	}

	//force d'attraction des villes sur le point j sur Y
	private double forceAttracVillesY(int j) {
		double force = 0.;

		for (int i = 0; i < N; i++) {
			double distance = villes.get(i).getY() - pointsElastique[j].getY();
			force += poids(i, j) * distance;
		}

		return alpha*force;
	}

	private double poids(int i, int j) {
		double poids = gausAttrac(Point_2D.distance(villes.get(i),pointsElastique[j]));
		//System.out.println("poids:"+poids);
		double denominateur = 0;
		for (int p = 0; p < nbPointsElas; p++) {
			denominateur += gausAttrac(Point_2D.distance(villes.get(i), pointsElastique[p]));
		}
		
		return denominateur>0?poids/denominateur:0;
	}

	private double gausAttrac(double x) {
		return Math.exp(-(x * x) / (2. * K * K));
	}
	
	private double calculLongueurElastique(){
		double longueur=0;
		for(int i=0;i<nbPointsElas;i++){
			Point_2D p1=pointsElastique[i];
			Point_2D p2=pointsElastique[(i+1)%nbPointsElas];
			longueur+=Point_2D.distance(p1, p2);
		}
		return longueur;
	}

	private void dessineToi() {
		int xs[] = new int[nbPointsElas];
		int ys[] = new int[nbPointsElas];
		zoneDessin.setColor(Color.BLACK);
		for (int i = 0; i < nbPointsElas; i++) {
			xs[i] = (int) pointsElastique[i].getX();
			ys[i] = (int) pointsElastique[i].getY();
			zoneDessin.fillOval(xs[i], ys[i], 2, 2);
		}
		zoneDessin.setColor(ite==nbIterations-1?Color.RED:Color.GREEN);
		if(ite==nbIterations-1){
			Graphics2D g2d=(Graphics2D)zoneDessin;
			g2d.setStroke(new BasicStroke(2));
			g2d.drawPolygon(xs, ys, nbPointsElas);
			polygonVilles.dessineToi(g2d);
		}else{
			zoneDessin.drawPolygon(xs, ys, nbPointsElas);
			polygonVilles.dessineToi(zoneDessin);
		}
	}

	@Override
	public synchronized void run() {
		// TODO Auto-generated method stub
		try {
			AlgoElastique.thNumber++;
			frame.algoStarted(AlgoElastique.thNumber);
			letsGo();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			System.out.println("thread stopped");
		}
		frame.algoEnded(AlgoElastique.thNumber);
	}
}
